{
  Copyright 2012 Sergey Ostanin

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
}

unit OrderQuestionScreen;

interface

uses
  Classes, SysUtils, FileUtil, Forms, Controls, ExtCtrls, TestUiUtils,
  OrderQuestion, TestCore, VisualUtils, DragSpace, PadWidget, Graphics,
  MiscUtils, Math, QuestionScreens, types;

type
  TOrderQuestionScreenFrame = class(TFrame, IQuestionScreen, IDragSpaceHost{ internal })
    pnlQuestion: TPanel;
    sbxQuestion: TScrollBox;
    procedure sbxQuestionMouseWheel(Sender: TObject; Shift: TShiftState;
      WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean);
  private type
    TShapeList = TGenericObjectList<TShape>;
  private
    FQuestion: TOrderQuestion;
    FFormulation: TPadWidget;
    FDragSpace: TDragSpaceFrame;
    FSequence: TPadWidgetList;
    FContainers: TShapeList;
    FDivider: TShape;
    FOldContainerIndex: Integer;
    function GetContainerIndexUnderItem(Widget: TPadWidget): Integer;
    { private declarations }
  public
    destructor Destroy; override;
    procedure SetUp(Question: TQuestion);
    procedure GoReadOnly;
    function GetQuestion: TQuestion;

    { IDragSpaceHost (internal) }
    procedure LayOut;
    procedure NotifyBeginDrag;
    procedure NotifyDragging;
    procedure NotifyEndDrag;

    { public declarations }
  end;

implementation

{$R *.lfm}

const
  VSPACING = 13;
  CONTAINER_MARGIN = 2;
  HSPACING = 10;
  DIVIDER_WIDTH = 2;
  EMPTY_CONTAINER_COLOR = $a0a0a0;
  OCCUPIED_CONTAINER_COLOR = $ffcc99;
  HOT_TRACK_CONTAINER_COLOR = $80ffb3;

{ TOrderQuestionScreenFrame }

procedure TOrderQuestionScreenFrame.sbxQuestionMouseWheel(Sender: TObject;
  Shift: TShiftState; WheelDelta: Integer; MousePos: TPoint;
  var Handled: Boolean);
begin
  FDragSpace.NotifyScroll(ScrollVerticallyByWheel(sbxQuestion, WheelDelta));
  Handled := TRUE;
end;

function TOrderQuestionScreenFrame.GetContainerIndexUnderItem(Widget: TPadWidget): Integer;
var
  n, p1, p2: Integer;

  function GetCommonVerticalPixels(ContainerIndex: Integer): Integer;
  var
    Container: TShape;
    y1, y2: Integer;
  begin
    if (ContainerIndex >= 0) and (ContainerIndex < FContainers.Count) then
    begin
      Container := FContainers[ContainerIndex];
      if IntersectSegments(Widget.Top, Widget.Top + Widget.Height,
        Container.Top, Container.Top + Container.Height, y1, y2) then
        Result := y2 - y1
      else
        Result := 0;
    end
    else
      Result := 0;
  end;

begin
  if (FContainers.Count > 0) and SegmentsOverlap(Widget.Left, Widget.Left + Widget.Width,
    FContainers.First.Left, FContainers.First.Left + FContainers.First.Width) then
  begin
    n := Widget.Top div (FContainers[0].Height + VSPACING);
    p1 := GetCommonVerticalPixels(n);
    p2 := GetCommonVerticalPixels(n + 1);
    if (p1 > 0) and (p1 >= p2) then
      Result := n
    else if (p2 > 0) and (p2 >= p1) then
      Result := n + 1
    else
      Result := -1;
  end
  else
    Result := -1;
end;

destructor TOrderQuestionScreenFrame.Destroy;
begin
  FreeAndNil(FSequence);
  FreeAndNil(FContainers);
  FreeAndNil(FDivider);
  FreeAndNil(FDragSpace);
  FreeAndNil(FFormulation);
  FreeAndNil(FQuestion);
  inherited;
end;

procedure TOrderQuestionScreenFrame.SetUp(Question: TQuestion);
var
  i: Integer;
  Widget: TPadWidget;
begin
  FQuestion := (Question as TOrderQuestion).Clone as TOrderQuestion;
  MakeVerticallyAutoScrollable(sbxQuestion);

  DisableAutoSizing;
  try
    FFormulation := TPadWidget.Create(nil);
    FFormulation.Border := TRUE;
    FFormulation.Parent := pnlQuestion;
    FFormulation.ReadOnly := TRUE;
    FFormulation.Model.Assign(FQuestion.Formulation);

    FDragSpace := TDragSpaceFrame.Create(nil);
    FDragSpace.Parent := pnlQuestion;

    FSequence := TPadWidgetList.Create;
    for i := 0 to FQuestion.Items.Count-1 do
    begin
      Widget := FSequence.AddSafely(TPadWidget.Create(nil));
      Widget.Model.Assign(FQuestion.Items[i]);
      Widget.Border := TRUE;
      Widget.ReadOnly := TRUE;
      Widget.Parent := FDragSpace;
      Widget.AddMouseListener(FDragSpace);
      Widget.Cursor := crSizeAll;
    end;

    FContainers := TShapeList.Create;
    for i := 0 to High(FQuestion.Response.GetAnswer) do
      FContainers.AddSafely(TShape.Create(nil)).Parent := FDragSpace;

    FDivider := TShape.Create(nil);
    FDivider.Parent := FDragSpace;
    FDivider.Width := DIVIDER_WIDTH;
    FDivider.Pen.Color := clGrayText;
    FDivider.AnchorParallel(akTop, 0, FDragSpace);
    FDivider.AnchorParallel(akBottom, 0, FDragSpace);

    FDragSpace.SetUp(Self);
  finally
    EnableAutoSizing;
  end;
end;

procedure TOrderQuestionScreenFrame.GoReadOnly;
var
  w: TPadWidget;
begin
  FDragSpace.DraggingEnabled := FALSE;
  for w in FSequence do
    w.Cursor := crDefault;
end;

function TOrderQuestionScreenFrame.GetQuestion: TQuestion;
begin
  Result := FQuestion;
end;

procedure TOrderQuestionScreenFrame.LayOut;
var
  ItemWidth, ItemHeight, ContainerWidth, ContainerHeight, i, y, ContainerIndex: Integer;
  Container: TShape;
  Widget: TPadWidget;
  Answer: TIntegerArray;
begin
  Answer := FQuestion.Response.GetAnswer;
  ItemWidth := Max((FDragSpace.ClientWidth - 2*CONTAINER_MARGIN
    - HSPACING - DIVIDER_WIDTH - HSPACING) div 2, 0);
  ContainerWidth := ItemWidth + 2*CONTAINER_MARGIN;
  FDivider.Left := ContainerWidth + HSPACING;

  ItemHeight := 0;
  for Widget in FSequence do
  begin
    Widget.Width := ItemWidth;
    ItemHeight := Max(ItemHeight, GetPreferredControlSize(Widget).cy);
  end;
  ContainerHeight := ItemHeight + 2*CONTAINER_MARGIN;

  { Place containers. }
  for i := 0 to High(Answer) do
  begin
    y := i*(ContainerHeight + VSPACING);
    Container := FContainers[i];
    Container.Width := ContainerWidth;
    Container.Height := ContainerHeight;
    Container.Top := y;
    if Answer[i] = -1 then
      Container.Brush.Color := EMPTY_CONTAINER_COLOR
    else
      Container.Brush.Color := OCCUPIED_CONTAINER_COLOR;
  end;

  { Place items. }
  for i := 0 to FSequence.Count-1 do
  begin
    Widget := FSequence[i];
    Widget.Height := ItemHeight;
    ContainerIndex := FindArrayInteger(Answer, i);
    if ContainerIndex = -1 then
    begin
      Widget.Left := ContainerWidth + HSPACING + DIVIDER_WIDTH + HSPACING;
      Widget.Top := i*(ContainerHeight + VSPACING) + CONTAINER_MARGIN;
    end
    else
    begin
      Container := FContainers[ContainerIndex];
      Widget.Left := Container.Left + CONTAINER_MARGIN;
      Widget.Top := Container.Top + CONTAINER_MARGIN;
    end;
  end;
end;

procedure TOrderQuestionScreenFrame.NotifyBeginDrag;
begin
  FOldContainerIndex := -1;
end;

procedure TOrderQuestionScreenFrame.NotifyDragging;
var
  ContainerIndex, Element, DraggingElement: Integer;
  Color: TColor;
  Answer: TIntegerArray;
begin
  DraggingElement := FSequence.IndexOf(FDragSpace.DragWidget);
  ContainerIndex := GetContainerIndexUnderItem(FDragSpace.DragWidget);
  Answer := FQuestion.Response.GetAnswer;

  if ContainerIndex <> FOldContainerIndex then
  begin
    if FOldContainerIndex <> -1 then
    begin
      Element := Answer[FOldContainerIndex];
      if (Element = -1) or (Element = DraggingElement) then
        Color := EMPTY_CONTAINER_COLOR
      else
        Color := OCCUPIED_CONTAINER_COLOR;
      FContainers[FOldContainerIndex].Brush.Color := Color;
    end;

    FOldContainerIndex := ContainerIndex;
    if ContainerIndex <> -1 then
      FContainers[ContainerIndex].Brush.Color := HOT_TRACK_CONTAINER_COLOR;
  end;
end;

procedure TOrderQuestionScreenFrame.NotifyEndDrag;
var
  ContainerIndex, ItemIndex, OldContainerIndex: Integer;
  Answer: TIntegerArray;
begin
  ContainerIndex := GetContainerIndexUnderItem(FDragSpace.DragWidget);
  ItemIndex := FSequence.IndexOf(FDragSpace.DragWidget);
  Answer := FQuestion.Response.GetAnswer;
  OldContainerIndex := FindArrayInteger(Answer, ItemIndex);

  if OldContainerIndex <> ContainerIndex then
  begin
    if OldContainerIndex <> -1 then
      Answer[OldContainerIndex] := -1;
    if ContainerIndex <> -1 then
    begin
      if OldContainerIndex <> -1 then
        Answer[OldContainerIndex] := Answer[ContainerIndex];
      Answer[ContainerIndex] := ItemIndex;
    end;
    FQuestion.Response.SetAnswer(Answer);
  end;
end;

initialization

  QuestionScreenRegistry.Add(TOrderQuestionScreenFrame, TOrderQuestion);

end.

